<?php
/**
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * @package Shipping_Agent
 * @copyright Copyright 2010 Structured Solutions
 * @license http://shippingagent.codeplex.com/license MIT License (MIT)
 * @version 3.7
 */

/**
 * SOAP classes from shippingagent.wsdl
 */
require_once("Soap.php");

/**
 * Implements the SOAP interface defined by shipping_agent.wsdl.
 * @package Shipping_Agent
 * @subpackage classes
 * @author Structured Solutions
 */
class StructuredSolutions_ShippingAgent_Model_Server
{
    /**
     * The contract version implemented by this class.
     * @var string
     */
    const VERSION = "3.7.0";

    /**
     * Configuration keys
     */
    const DOWNLOAD_ORDERS = "shippingagent/general/download_orders";
    const INSURANCE_VALUE = "shippingagent/general/insurance_value";
	const PAYMENT_STATUS_UNPAID = "shippingagent/payment_status/unpaid";
	const PAYMENT_STATUS_PARTIALLY_PAID = "shippingagent/payment_status/partially_paid";
	const PAYMENT_STATUS_PAID = "shippingagent/payment_status/paid";
    const RESIDENTIAL = "shippingagent/general/residential";
    const SEND_SHIPMENT_NOTICE = "shippingagent/general/send_shipment_notice";
	const SHIPPING_STATUS_UNSHIPPABLE = "shippingagent/shipping_status/unshippable";
	const SHIPPING_STATUS_UNSHIPPED = "shippingagent/shipping_status/unshipped";
	const SHIPPING_STATUS_PARTIALLY_SHIPPED = "shippingagent/shipping_status/partially_shipped";
	const SHIPPING_STATUS_SHIPPED = "shippingagent/shipping_status/shipped";

    /**
     * Access to credentials passed in by header.
     * @var Credentials $CredentialsValue
     */
    public $CredentialsValue = null;

    /**
     * Process incomming Credentials header.
     * @param stdClass $header 
     */
    public function Credentials(stdClass $header)
    {
        $this->CredentialsValue = $header;
    }

    /**
     * Return the features supported by this implementation of Shipping Agent.
     */
    public function GetFeatures()
    {
        $this->authenticateUser();

        $response = new GetFeaturesResponse();
        $response->GetFeaturesResult = new Features();
        $response->GetFeaturesResult->CanSearchByOrderDate = true;
        $response->GetFeaturesResult->CanSearchByOrderNumber = true;
        $response->GetFeaturesResult->CanSearchByOrderStatus = true;
        $response->GetFeaturesResult->CanSearchByPaymentStatus = false;
        return $response;
    }
    
	/**
	 * Return new and updated orders. In this implementation the pageKey is
     * the date and time of the most recent order update. If pageKey is null,
     * the current server date will be used.
     *
     * NOTE: This may return order(s) from the previous call. That is because
     * the date/time comparison is only accurate to the nearest second. On a busy
     * site with a fast processor, it is possible that an order will be
     * updated in the same second as the previous call but after the previous
     * call has finished.
	 *
	 * @param stdClass $parameters
     * $parameters->pageKey has both the last order id and the last updated_at
     * date and time from the previous call. This call will return orders that
     * have been updated on the last updated_at with order id <> last order id
     * AND orders that have been updated after the last updated_at.
	 * $parameters->maxCount is the maximum number of orders to return.
     * @return GetNewOrdersResponse
	 *
	 * If Remaining > 0, the client will typically call this method again to
     * retrieve the next page of results.
	 */
	public function GetNewOrders(stdClass $parameters)
	{
        $this->authenticateUser();

		try
		{
			$mcOrders = $this->buildOrderCollection($parameters->orderStatusList);

			if (isset($parameters->pageKey) && !empty($parameters->pageKey)) {
                $pageKeys = explode("|", $parameters->pageKey);
                if (count($pageKeys) != 2) {
                    throw new Exception("Invalid page key.");
                }
                /**
                 * Find orders that have been updated on the last updated_at
                 * date and time with order id <> the last order id - AND -
                 * orders that have been updated after the last updated_at.
                 */
                $condition =
                    "(e.updated_at = '" . $pageKeys[1] ."' AND " .
                    "e.entity_id <> " . $pageKeys[0] . ") " .
                    " OR (e.updated_at > '" . $pageKeys[1] . "')";
                $mcOrders->getSelect()->where($condition);
			} else {
                /**
                 * Find orders that were updated today.
                 */
                $mcOrders->addAttributeToFilter(
                    "updated_at", array("date"=>true, "from"=>new Zend_Date()));
            }

            if ($mcOrders->getSize() == 0) {
                return null;
            }

		    $response = new GetNewOrdersResponse();
		    $response->GetNewOrdersResult = new OrderPage();

            $lastOrderId = 0;
            $lastUpdate = "";
			foreach ($mcOrders->getAllIds($parameters->maxCount) as $mcOrderId) 
            {
                $response->GetNewOrdersResult->Orders[] = $mcOrderId;

		    	$mcOrder = Mage::getModel('sales/order')->load($mcOrderId);
                if ($mcOrder->getUpdatedAt() > $lastUpdate) {
                    $lastOrderId = $mcOrderId;
                    $lastUpdate = $mcOrder->getUpdatedAt();
                }
			}

			$response->GetNewOrdersResult->Key = $lastOrderId . "|" . $lastUpdate;
			$response->GetNewOrdersResult->Remaining =
				$mcOrders->getSize() -
				count($response->GetNewOrdersResult->Orders);
			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot return new and updated orders. " . $ex->getMessage());
		}
	}

	/**
	 * Return one Order.
	 * $parameters->orderId is the ID of the order to retrieve.
	 * @param stdClass $parameters
	 * @return GetOrderResponse
	 */
	public function GetOrder(stdClass $parameters)
	{
        $this->authenticateUser();

		if (!isset($parameters->orderId) || trim($parameters->orderId) == '')
			throw new SoapFault("Client", "Missing order ID.");

		try
		{
			$mcOrder = Mage::getModel('sales/order')->load($parameters->orderId);
			if ($mcOrder->getId() != $parameters->orderId)
				throw new Exception("Invalid Order ID '" . $parameters->orderId . "'.");

			$response = new GetOrderResponse();
			$response->GetOrderResult = $this->buildOrderResult($mcOrder);

			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve order. " . $ex->getMessage());
		}
	}

    /**
     * Return the list of searchable order statuses.
     */
    public function GetOrderStatuses()
    {
        $this->authenticateUser();

        $response = new GetOrderStatusesResponse();

        $config = Mage::getSingleton('sales/order_config');
        foreach ($config->getStatuses() as $statusCode=>$statusName)
        {
            $orderStatus = new OrderStatus();
            $orderStatus->Name = $statusName;
            $orderStatus->OrderStatusId = $statusCode;
            $response->GetOrderStatusesResult[] = $orderStatus;
        }

        return $response;;
    }

	/**
	 * Return the orders that meet the criteria specified in the parameters.
	 * $parameters->startOrderNumber is the first order number to retrieve. If null, start at the first order.
	 *
	 * $parameters->endOrderNumber is the last order number to retrieve. If null, end at the most recent order.
	 *
	 * $parameters->startOrderDate is the beginning of the date range to search. If null, ignore the beginning date.
	 *
	 * $parameters->endOrderDate is the end of the date range to search. If null, ignore the end date. The
	 * endOrderDate does not include a time component, but the client assumes the result will include orders placed
	 * on the endOrderDate.
     *
     * $parameters->orderStatusList is the list of order status values to include.
     *
     * $parameters->paymentStatusList is ignored.
	 *
	 * $parameters->pageKey is a key used by Shipping Agent to determine which page of the results to return. If null, return the first page.
	 *
	 * $parameters->maxCount is the maximum number of orders to return.
	 *
	 * @param stdClass $parameters
	 * @return GetOrdersByRangeResponse
	 *
	 * If Remaining > 0, the client will call this method again to retrieve the
	 * next page of results.
	 */
	public function GetOrdersByRange(stdClass $parameters)
	{
        $this->authenticateUser();

		try
		{
			$mcOrders = $this->buildOrderCollection($parameters->orderStatusList);

			if (isset($parameters->startOrderNumber) && !empty($parameters->startOrderNumber))
			{
				$mcOrders->addAttributeToFilter(
                    "increment_id", array("from"=>$parameters->startOrderNumber));
			}

			if (isset($parameters->endOrderNumber) && !empty($parameters->endOrderNumber))
			{
				$mcOrders->addAttributeToFilter(
                    "increment_id", array("to"=>$parameters->endOrderNumber));
			}

			if (isset($parameters->startOrderDate) && !empty($parameters->startOrderDate))
			{
				/**
				 * The UTC date from Shipper does not include the timezone for
                 * the website, even though the human on the other end thinks of
                 * it that way. So this code calculates the GMT offset for the
                 * date and then adds that back in.
				 */
		        $date = new Zend_Date();
		        $date->setIso($parameters->startOrderDate);
		        $offset = Mage::app()->getLocale()
                    ->storeDate(Mage::app()->getStore(), $date, true)
                    ->getGmtOffset();
		        $date->addTimestamp($offset);
		        $mcOrders->addAttributeToFilter(
                    "created_at", array("datetime"=>true, "from"=>$date));
			}

			if (isset($parameters->endOrderDate) && !empty($parameters->endOrderDate))
			{
				/**
				 *  The UTC date from Shipper does not include the timezone for the website,
				 *  even though the human on the other end thinks of it that way. So this code
				 *  calculates the GMT offset for the date and then adds that back in.
				 */
		        $date = new Zend_Date();
		        $date->setIso($parameters->endOrderDate);
		        $date->addDay(1);
		        $offset = Mage::app()->getLocale()
                    ->storeDate(Mage::app()->getStore(), $date, true)
                    ->getGmtOffset();
		        $date->addTimestamp($offset);
		        $mcOrders->addAttributeToFilter(
                    "created_at", array("datetime"=>true, "to"=>$date));
			}

			if (isset($parameters->pageKey) && $parameters->pageKey != "")
			{
				$mcOrders->addAttributeToFilter(
                    $mcOrders->getEntity()->getIdFieldName(),
                    array("gt"=>$parameters->pageKey));
			}

            if ($mcOrders->getSize() == 0) {
                return null;
            }

		    $response = new GetOrdersByRangeResponse();
		    $response->GetOrdersByRangeResult = new OrderPage();

		    // Through 1.3, getAllIds ignores explicit sort order
			// $mcOrders->setOrder($mcOrders->getEntity()->getIdFieldName(),
            //      Varien_Data_Collection::SORT_ORDER_ASC);
		    foreach ($mcOrders->getAllIds($parameters->maxCount) as $mcOrderId) 
            {
                $response->GetOrdersByRangeResult->Orders[] = $mcOrderId;
			}

			$response->GetOrdersByRangeResult->Key = $mcOrderId;
			$response->GetOrdersByRangeResult->Remaining =
				$mcOrders->getSize() -
				count($response->GetOrdersByRangeResult->Orders);
			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve orders by range. " . $ex->getMessage());
		}
	}

	/**
	 * Return one package.
	 *
	 * $parameters->orderId is the ID of the Magento order the package belongs to.
	 * $parameters->packageId is the ID of the Magento Track. The Magento Track
     * is identified by the Shipment ID and Track ID separately by a comma. For
     * example, "1,1". If the packageId does not contain a comma, then return
     * the default shipment for the order.
	 *
	 * @param stdClass $parameters
	 * @return GetPackageResponse
	 */
	public function GetPackage(stdClass $parameters)
	{
        $this->authenticateUser();

		if (!isset($parameters->orderId) || trim($parameters->orderId) == '')
			throw new SoapFault("Client", "Missing order ID.");

		try
		{
			$mcOrder = Mage::getModel('sales/order')->load($parameters->orderId);
			if ($mcOrder->getId() != $parameters->orderId) {
				throw new Exception("Invalid Order ID '" . $parameters->orderId . "'.");
			}

			if (isset($parameters->packageId) && strpos($parameters->packageId, ",")) {
                $ids = explode(",", $parameters->packageId);
                $mcShipmentId = trim($ids[0]);
                $mcTrackId = trim($ids[1]);
            }

			if (isset($mcShipmentId)) {
				$mcShipment = Mage::getModel('sales/order_shipment')->load($mcShipmentId);
				if ($mcShipment->getId() != $mcShipmentId) {
					throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
			} else {
				$mcShipment = $this->buildShipment($mcOrder);
				if ($mcShipment == null) {
					$response = new GetPackageResponse();
					$response->GetPackageResult = new PackageResult();
					$response->GetPackageResult->Success = true;
					return $response;
                }
			}

            if (isset($mcTrackId)) {
                $mcTrack = Mage::getModel("sales/order_shipment_track")->load($mcTrackId);
                if ($mcTrack->getId() != $mcTrackId || $mcTrack->getShipment()->getId() != $mcShipmentId) {
                    throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
            } else if (count($mcShipment->getAllTracks()) == 0) {
                $mcTrack = Mage::getModel("sales/order_shipment_track");
                $mcShipment->addTrack($mcTrack);
            } else {
                $mcTracks = $mcShipment->getAllTracks();
                $mcTrack = $mcTracks[0];
            }

            $response = new GetPackageResponse();
            $response->GetPackageResult = $this->buildPackageResult($mcTrack);
            return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve package. " . $ex->getMessage());
		}
	}

    /**
     * Return the list of searchable order payment statuses.
     */
    public function GetPaymentStatuses()
    {
        $this->authenticateUser();

        return null;
    }
    
	/**
	 * Return the complete list of shipping rate providers supported by this cart.
	 * @return List of shipping rate providers enabled in this cart
	 */
	public function GetRateProviders()
	{
        $this->authenticateUser();

        try
        {
            $providers = array();

            $mcCarriers = Mage::getSingleton('shipping/config')->getAllCarriers();
            foreach ($mcCarriers as $mcCarrierCode => $mcCarrier) {
                if ($mcCarrierCode != 'googlecheckout' && $mcCarrier->isActive()) {
                    $provider = new RateProvider();
                    $provider->Name = $mcCarrier->getConfigData('title');
                    $mcMethods = $mcCarrier->getAllowedMethods();
                    foreach ($mcMethods as $mcMethod)
                    {
                        $provider->RateOptions[] = $mcMethod;
                    }
                    $providers[] = $provider;
                }
            }

            $response = new GetRateProvidersResponse();
            $response->GetRateProvidersResult = $providers;
            return $response;
        }
        catch (Exception $ex)
        {
			throw new SoapFault("Server", "Cannot retrieve rate providers. " . $ex->getMessage());
        }
	}

	/**
	 * Return the complete list of tracking providers supported by this cart.
	 * @return GetTrackingProvidersResponse
	 */
	public function GetTrackingProviders()
	{
        $this->authenticateUser();

		try
		{
            $providers = array();

            $mcCarriers= Mage::getSingleton('shipping/config')->getAllCarriers();
            foreach ($mcCarriers as $mcCarrierCode => $mcCarrier) {
                if ($mcCarrier->isTrackingAvailable()) {
                    $provider = new TrackingProvider();
                    $provider->Name = $mcCarrier->getConfigData('title');
                    $provider->TrackingProviderId = $mcCarrierCode;
                    $providers[] = $provider;
                }
            }
    
			$response = new GetTrackingProvidersResponse();
			$response->GetTrackingProvidersResult = $providers;
			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot retrieve tracking providers. " . $ex->getMessage());
		}
	}

	/**
	 * Return the WSDL version implemented by this Shipping Agent.
	 * @return string
	 */
	public function GetVersion()
	{
        $this->authenticateUser();

		$response = new GetVersionResponse();
		$response->GetVersionResult = self::VERSION;
		return $response;
	}

	/**
	 * Update the package with tracking information.
	 * @param stdClass $parameters
	 * @return UpdatePackageResponse
	 */
	public function UpdatePackage(stdClass $parameters)
	{
		if (!isset($parameters->update))
			return null;

		if (!isset($parameters->update->OrderId) || trim($parameters->update->OrderId) == '')
			throw new SoapFault("Client", "Missing order ID.");

		try
		{
			$response = new UpdatePackageResponse();
			$response->UpdatePackageResult = $this->buildUpdateResult($parameters->update);

			return $response;
		}
		catch (Exception $ex)
		{
			throw new SoapFault("Server", "Cannot update package. " . $ex->getMessage());
		}
	}

	/**
	 * Compare two addresses and return true if equal.
	 * @param stdClass $address
	 * @param Mage_Sales_Model_Order_Address $mcAddress
	 */
	private function addressesAreEqual(stdClass $address, Mage_Sales_Model_Order_Address $mcAddress)
	{
		if (isset($address->City)) {
            if ($address->City != $mcAddress->getCity()) return false;
        } else {
            if ("" != $mcAddress->getCity()) return false;
        }
		if (isset($address->Company)) {
            if ($address->Company != $mcAddress->getCompany()) return false;
        } else {
            if ("" != $mcAddress->getCompany()) return false;
        }
		if (isset($address->Country)) {
            if ($address->Country != $mcAddress->getCountry()) return false;
        } else {
            if ("" != $mcAddress->getCountry()) return false;
        }
		if (isset($address->Fax)) {
            if ($address->Fax != $mcAddress->getFax()) return false;
        } else {
            if ("" != $mcAddress->getFax()) return false;
        }
		if (isset($address->FirstName)) {
            if ($address->FirstName != $mcAddress->getFirstname()) return false;
        } else {
            if ("" != $mcAddress->getFirstname()) return false;
        }
		if (isset($address->LastName)) {
            if ($address->LastName != $mcAddress->getLastname()) return false;
        } else {
            if ("" != $mcAddress->getLastname()) return false;
        }
		if (isset($address->Line1)) {
            if ($address->Line1 != $mcAddress->getStreet1()) return false;
        } else {
            if ("" != $mcAddress->getStreet1()) return false;
        }
		if (isset($address->Line2)) {
            if ($address->Line2 != $mcAddress->getStreet2()) return false;
        } else {
            if ("" != $mcAddress->getStreet2()) return false;
        }
        if (isset($address->Phone)) {
            if ($address->Phone != $mcAddress->getTelephone()) return false;
        } else {
            if ("" != $mcAddress->getTelephone()) return false;
        }
		if (isset($address->PostalCode)) {
            if ($address->PostalCode != $mcAddress->getPostcode()) return false;
        } else {
            if ("" != $mcAddress->getPostcode()) return false;
        }
		if (isset($address->Region)) {
            if ($address->Region != $mcAddress->getRegionCode()) return false;
        } else {
            if ("" != $mcAddress->getRegionCode()) return false;
        }
		return true;
	}

    private function authenticateUser()
    {
        if ($this->CredentialsValue == null)
            throw new SoapFault("Client", "You must supply a valid username and password.");

        try
        {
            $user = Mage::getModel('admin/user')->login(
                $this->CredentialsValue->Username,
                $this->CredentialsValue->Password
            );
        }
        catch (Exception $ex)
        {
            throw new SoapFault("Client", "The username and password are not authorized to access Shipping Agent.");
        }
        if ($user->getId() == null)
            throw new SoapFault("Client", "The username and password are not authorized to access Shipping Agent.");
    }

	/**
	 * Build a shipping address from an order address array.
	 * Note that Shipper expects 2 letter country, state, and
	 * province codes.
	 * @param Mage_Sales_Model_Order_Address $mcAddress
	 * @return ShippingAddress
	 */
	private function buildAddress(Mage_Sales_Model_Order_Address $mcAddress)
	{
		$address = new Address();
		$address->AddressId = $mcAddress->getId();
		$address->City = $mcAddress->getCity();
		$address->Company = $mcAddress->getCompany();
		$address->Country = $mcAddress->getCountry();
		$address->Email = $mcAddress->getEmail();
		$address->Fax = $mcAddress->getFax();
		$address->FirstName = $mcAddress->getFirstname();
		$address->LastName = $mcAddress->getLastname();
		$address->Line1 = $mcAddress->getStreet1();
		$address->Line2 = $mcAddress->getStreet2();
		$address->Phone = $mcAddress->getTelephone();
		$address->PostalCode = $mcAddress->getPostcode();
		$address->Region = $mcAddress->getRegionCode();
		return $address;
	}

	/**
	 * Build a list of orders charges.
	 * Discounts are included as order items. Gift Certificates are treated as payments.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array
	 */
	private function buildCharges(Mage_Sales_Model_Order $mcOrder)
	{
		$charges = array();
		$amount = $this->getFloat($mcOrder->getShippingAmount());
		if ($amount != (float) 0)
		{
			$namedAmount = new NamedAmount();
			$namedAmount->Amount = $amount;
			$namedAmount->Name = "Shipping";
			$charges[] = $namedAmount;
		}
		$amount = $this->getFloat($mcOrder->getTaxAmount());
		if ($amount != (float) 0)
		{
			$namedAmount = new NamedAmount();
			$namedAmount->Amount = $amount;
			$namedAmount->Name = "Tax";
			$charges[] = $namedAmount;
		}

		return $charges;
	}

	/**
	 * Build a Customer from an order.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return Customer
	 */
	private function buildCustomer(Mage_Sales_Model_Order $mcOrder)
	{
		$customer = new Customer();
		if ($mcOrder->getCustomerIsGuest())
		{
			$customer->CustomerId = "guest";
		}
		else
		{
			$customer->CustomerId = $mcOrder->getCustomerId();
			$customer->CustomerUrl = $this->buildCustomerUrl("index.php/customer/account/");
			$customer->Email = $mcOrder->getCustomerEmail();
			$customer->FirstName = $mcOrder->getCustomerFirstname();
			$customer->LastName = $mcOrder->getCustomerLastname();
			$customer->MerchantUrl = $this->buildMerchantUrl("index.php/admin/customer/edit/id/" . $mcOrder->getCustomerId() . "/");
			$customer->UserName = $mcOrder->getCustomerEmail();
		}
		return $customer;
	}

	/**
	 * Build a URL to the customer's view of the page.
	 * @param string $page
	 * @param string $query
	 * @return string
	 */
	private function buildCustomerUrl($page, $query = null)
	{
		$url = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB) . $page;
		if (isset($query) && trim($query) != "")
			$url = $url . "?" . $query;
		return $url;
	}

	/**
	 * Return the Magento Item SKU
	 *
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return string
	 */
	private function buildItemCode(Mage_Sales_Model_Order_Item $mcItem)
	{
        if ($mcItem->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
            return $mcItem->getProductOptionByCode('simple_sku');
        }
        return $mcItem->getSku();
	}

	private function buildItemInventory(Mage_Sales_Model_Order_Item $mcOrderItem)
	{
		if ($mcOrderItem->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
			$children = $mcOrderItem->getChildrenItems();
        	$mcStockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($children[0]->getProductId());
		}
		else {
        	$mcStockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($mcOrderItem->getProductId());
		}
        if ($mcStockItem->getId() && Mage::helper('catalogInventory')->isQty($mcStockItem->getTypeId())) {
			return $mcStockItem->getQty();
        }
        return 0;
	}

	/**
	 * Return an array of order item options.
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return array
	 */
    private function buildItemOptions(Mage_Sales_Model_Order_Item $mcItem)
    {
        $result = array();
        $options = $mcItem->getProductOptions();
        if ($options) {
            if (isset($options['options'])) {
                $result = array_merge($result, $options['options']);
            }
            if (isset($options['additional_options'])) {
                $result = array_merge($result, $options['additional_options']);
            }
            if (!empty($options['attributes_info'])) {
                $result = array_merge($options['attributes_info'], $result);
            }
            if (!empty($options["bundle_options"])) {
                $result = array_merge($result, $options["bundle_options"]);
            }
        }
        return $result;
    }

	/**
	 * Build up the item properties.
	 *
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return string
	 */
	private function buildItemProperties(Mage_Sales_Model_Order_Item $mcItem)
	{
        $properties = array();

		foreach ($this->buildItemOptions($mcItem) as $option) {
            $label = $option["label"];
            $value = $option["value"];
            if (is_array($value)) {
                $_desc = array();
                foreach ($value as $_value) {
                    $_desc[] = $_value["qty"] . "x" . $_value["title"];
                }
                $value = implode("; ", $_desc);
            }
            $property = new Property();
            $property->Name = $label;
            $property->Value = $value;
            $properties[] = $property;
		}

		return $properties;
	}

	/**
	 * Create an ItemResult from a Magento item.
	 *
	 * @param Mage_Sales_Model_Order_Item $mcItem
	 * @return ItemResult
	 */
	private function buildItemResult(Mage_Sales_Model_Order_Item $mcOrderItem)
	{
        $itemResult = new ItemResult();
        $item = new ShippingAgentItem();
        $itemResult->Item = $item;

		$item->ItemId = $mcOrderItem->getId();
        try
        {
            $item->Code = $this->buildItemCode($mcOrderItem);
            $item->Inventory = $this->buildItemInventory($mcOrderItem);
            $item->LineTotal = $mcOrderItem->getRowTotal();
            $item->Name = $mcOrderItem->getName();
            $item->Price = $mcOrderItem->getPrice();
            $item->Properties = $this->buildItemProperties($mcOrderItem);
            $item->Quantity = $mcOrderItem->getQtyOrdered();
            $item->Shippable = $mcOrderItem->getIsVirtual() == 0;
            $item->Weight = $mcOrderItem->getQtyOrdered() * $mcOrderItem->getWeight();

            $itemResult->Success = true;
        }
        catch (Exception $ex)
        {
            $itemResult->Success = false;
            $itemResult->Message = $ex->getMessage();
        }
		return $itemResult;
	}

    private function buildMageDate($time)
    {
        return Mage::app()->getLocale()->date($time, null, null, true);
    }

	/**
	 * Build a URL to the merchant's view of the page.
	 * @param string $page
	 * @param string $query
	 * @return string
	 */
	private function buildMerchantUrl($page, $query = null)
	{
		$url = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, true) . $page;
		if (isset($query) && trim($query) != "")
			$url = $url . "?" . $query;
		return $url;
	}

    /**
     * Build an order collection with configuration filters applied.
     * @return Mage_Sales_Model_Mysql4_Order_Collection
     */
    private function buildOrderCollection($orderStatusList)
    {
        // Determine which orders to include
        $mcOrders = Mage::getModel('sales/order')->getCollection();
        if (!empty($orderStatusList))
        {
            $mcOrders->addAttributeToFilter("status",
                array("in"=>explode(",", $orderStatusList)));
        }

        // Determine which stores to include
        $ids = array();
        foreach (Mage::getResourceModel('core/store_collection') as $store)
        {
            if (Mage::getStoreConfig(self::DOWNLOAD_ORDERS, $store))
            {
                $ids[] = $store->getId();
            }
        }
        $mcOrders->addAttributeToFilter("store_id", array("in"=>$ids));

        return $mcOrders;
    }

	/**
	 * Build a list of Items from the order.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array
	 */
	private function buildOrderItemResults(Mage_Sales_Model_Order $mcOrder)
	{
		$items = array();

		foreach ($mcOrder->getAllItems() as $mcOrderItem)
		{
			if ($mcOrderItem->isDummy(false)) {
				continue;
			}
            $items[] = $this->buildItemResult($mcOrderItem);
		}

		return $items;
	}

	/**
	 * Build a list of order payments.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array]
	 */
	private function buildOrderPayments(Mage_Sales_Model_Order $mcOrder)
	{
		$payments = array();

		foreach ($mcOrder->getInvoiceCollection() as $mcInvoice) {
			if ($mcInvoice->getState() == Mage_Sales_Model_Order_Invoice::STATE_PAID) {
				$namedAmount = new NamedAmount();
				$namedAmount->Amount = (float) $mcInvoice->getGrandTotal();
				$namedAmount->Name = "Invoice #" . $mcInvoice->getIncrementId();
				$payments[] = $namedAmount;
			}
		}

		foreach ($mcOrder->getCreditmemosCollection() as $mcCredit)
		{
			if ($mcCredit->getState() == Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED) {
				$namedAmount = new NamedAmount();
				$namedAmount->Amount = - ((float) $mcCredit->getGrandTotal());
				$namedAmount->Name = "Refund #" . $mcCredit->getIncrementId();
				$payments[] = $namedAmount;
			}
		}

		return $payments;
	}

    /**
     * Build a list of Magento specific properties for the order.
     * @param Mage_Sales_Model_Order $mcOrder
     * @return array
     */
    private function buildOrderProperties(Mage_Sales_Model_Order $mcOrder) {
        $properties = array();

        $storeId = $mcOrder->getStoreId();
        if (is_null($storeId)) {
            $mcNames = explode("\n", $mcOrder->getStoreName());
        } else {
            $store = Mage::app()->getStore($storeId);
            $mcNames = array(
                $store->getWebsite()->getName(),
                $store->getGroup()->getName(),
                $store->getName()
            );
        }

        if (isset($mcNames[0])) {
            $property = new Property();
            $property->Name = "Website Name";
            $property->Value = $mcNames[0];
            $properties[] = $property;
        }

        if (isset($mcNames[1])) {
            $property = new Property();
            $property->Name = "Store Name";
            $property->Value = $mcNames[1];
            $properties[] = $property;
        }

        if (isset($mcNames[2])) {
            $property = new Property();
            $property->Name = "Store View Name";
            $property->Value = $mcNames[2];
            $properties[] = $property;
        }

        return $properties;
    }

	/**
	 * Build an OrderResult from a Magento Commerce order.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return OrderResult
	 */
	private function buildOrderResult(Mage_Sales_Model_Order $mcOrder)
	{
		$orderResult = new OrderResult();
		$order = new ShippingAgentOrder();
        $orderResult->Order = $order;

		$order->OrderId = $mcOrder->getId();

		try
		{
			$order->BillingAddress = $this->buildAddress($mcOrder->getBillingAddress());
			$order->Charges = $this->buildCharges($mcOrder);
			$order->Customer = $this->buildCustomer($mcOrder);
			$order->CustomerUrl = $this->buildCustomerUrl("index.php/sales/order/view/order_id/" . $mcOrder->getId() . "/");
			$order->Items = $this->buildOrderItemResults($mcOrder);
            $order->LastUpdated = $this->buildMageDate($mcOrder->getResource()->mktime($mcOrder->getUpdatedAt()))->getIso();
			$order->MerchantUrl = $this->buildMerchantUrl("index.php/admin/sales_order/view/order_id/" . $mcOrder->getId() . "/");
			$order->OrderDate = $mcOrder->getCreatedAtDate()->getIso();
			$order->OrderNumber = $mcOrder->getRealOrderId();
			$order->OrderStatus = $mcOrder->getStatusLabel();
			$order->OrderTotal = (float) $mcOrder->getGrandTotal();
			$order->Packages = $this->buildPackageRefs($mcOrder);
			$order->Payments = $this->buildOrderPayments($mcOrder);
			$order->PaymentStatus = $this->buildPaymentStatus($mcOrder);
            $order->Properties = $this->buildOrderProperties($mcOrder);
			$order->ShippingStatus = $this->buildShippingStatus($mcOrder);
            
			$orderResult->Success = true;
		}
		catch (Exception $ex)
		{
			$orderResult->Success = false;
			$orderResult->Message = $ex->getMessage();
		}

		return $orderResult;
	}

	/**
	 * Calculate the insurance value of the items.
	 *
	 * @param Mage_Sales_Model_Order_Shipment $mcShipment
	 * @return float
	 */
	private function buildPackageInsuranceValue(Mage_Sales_Model_Order_Shipment $mcShipment)
	{
		$value = (float) 0;

        $insuranceValue = Mage::getStoreConfig(self::INSURANCE_VALUE, $mcShipment->getStore());

		foreach ($mcShipment->getAllItems() as $mcShipmentItem)
		{
            switch ($insuranceValue) {
                case "Cost":
                    // $mcShipmentItem->getOrderItem()->getCost() is always 0
                    $product = Mage::getModel('catalog/product')->load($mcShipmentItem->getProductId());
                    $value += $mcShipmentItem->getQty() * $product->getCost();
                    break;
                default:
                    $value += $mcShipmentItem->getQty() * $mcShipmentItem->getPrice();
            }
		}

		return (float) $value;
	}

	/**
	 * Build a list of Items from the order.
	 *
	 * @param Mage_Sales_Model_Order_Shipment $mcShipment
	 * @return array
	 */
	private function buildPackageItemResults(Mage_Sales_Model_Order_Shipment_Track $mcTrack)
	{
        $mcShipment = $mcTrack->getShipment();

		$items = array();

		foreach ($mcShipment->getAllItems() as $mcShipmentItem)
		{
			$mcOrderItem = $mcShipmentItem->getOrderItem();
			if ($mcOrderItem->isDummy(true) || $mcOrderItem->getIsVirtual()) {
                continue;
			}
            $itemResult = $this->buildItemResult($mcOrderItem);
            $itemResult->Item->LineTotal = $mcShipmentItem->getQty() * $itemResult->Item->Price;
            $itemResult->Item->Quantity = $mcShipmentItem->getQty();
            $itemResult->Item->Weight = $mcShipmentItem->getQty() * $mcOrderItem->getWeight();
            $items[] = $itemResult;
		}

		return $items;
	}

    /**
     * Build a package ID from the Magento Track. The Magento Track
     * is identified by the Shipment ID and Track ID separately by a comma. For
     * example, "1,1". To identify the default package for the order, return
     * a package ID without a comma.
     *
     * @param Mage_Sales_Model_Order_Shipment_Track $mcTrack
     * @return string
     */
    private function buildPackageId(Mage_Sales_Model_Order_Shipment_Track $mcTrack)
    {
        $mcShipment = $mcTrack->getShipment();
        return $mcShipment->getId() ? $mcShipment->getId() . "," . $mcTrack->getId() : "default";
    }

	/**
	 * Build a list of PackageRefs from a Magento Commerce order.
	 * One package for each Track.
	 *
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return array
	 */
	private function buildPackageRefs(Mage_Sales_Model_Order $mcOrder)
	{
		$packages = array();

        $mcShipments = array();
        foreach ($mcOrder->getShipmentsCollection() as $mcShipment) {
            if (!$mcShipment->isDeleted()) {
                $mcShipments[] = $mcShipment;
                if (count($mcShipment->getAllTracks()) == 0) {
                    $mcTrack = Mage::getModel('sales/order_shipment_track');
                    $mcShipment->addTrack($mcTrack);
                    $mcShipment->save();
                }
            }
        }

        // Create one more shipment to capture all the remaining
        // unshipped items. If this is the first time through, this
        // will include all the shippable items.
        $mcShipment = $this->buildShipment($mcOrder);
        if ($mcShipment != null) {
            $mcShipments[] = $mcShipment;
            $mcTrack = Mage::getModel('sales/order_shipment_track');
            $mcShipment->addTrack($mcTrack);
        }

        // Create one Package for each tracking number.
		foreach ($mcShipments as $mcShipment) {
			foreach ($mcShipment->getAllTracks() as $mcTrack) {
                $packageRef = new PackageRef();
                $packageRef->OrderId = $mcOrder->getId();
                $packageRef->PackageId = $this->buildPackageId($mcTrack);
				$packages[] = $packageRef;
			}
		}
		return $packages;
	}

	/**
	 * Build a PackageResult from a Magento Commerce order. There is one
	 * package per Magento shipment.
	 *
	 * @param Mage_Sales_Model_Order_Shipment_Track $mcTrack
	 * @return PackageResult
	 */
	private function buildPackageResult(Mage_Sales_Model_Order_Shipment_Track $mcTrack)
	{
        $mcShipment = $mcTrack->getShipment();

		$packageResult = new PackageResult();
        $package = new Package();
        $packageResult->Package = $package;

        $package->OrderId = $mcShipment->getOrder()->getId();
		$package->PackageId = $this->buildPackageId($mcTrack);

		try
		{
			if ($mcShipment->getId()) {
				$package->CustomerUrl = $this->buildCustomerUrl("index.php/sales/order/shipment/order_id/" . $mcShipment->getOrder()->getId() . "/");
			}
			$package->Destination = $this->buildShippingAddress($mcShipment->getOrder()->getShippingAddress());
			$package->Instructions = $mcShipment->getOrder()->getCustomerNote();
			$package->InsuranceValue = $this->buildPackageInsuranceValue($mcShipment);
			$package->Items = $this->buildPackageItemResults($mcTrack);
			if ($mcShipment->getId()) {
				$package->MerchantUrl = $this->buildCustomerUrl("index.php/admin/sales_order_shipment/view/shipment_id/" . $mcShipment->getId() . "/order_id/" . $mcShipment->getOrder()->getId() . "/");
			}
			else {
				$package->MerchantUrl = $this->buildCustomerUrl("index.php/admin/sales_order/view/order_id/" . $mcShipment->getOrder()->getId() . "/");
			}
            $package->RateOptionName = $this->buildRateOptionName($mcShipment);
            $package->RateProviderName = $this->buildRateProviderName($mcShipment);
			if ($mcShipment->hasCreatedAt()) {
				$package->ShipDate = $mcShipment->getCreatedAtDate()->getIso();
				$package->Shipped = true;
				$package->TrackingNumber = $mcTrack->getNumber();
				$carrierInstance = Mage::getSingleton('shipping/config')->getCarrierInstance($mcTrack->getCarrierCode());
				if ($carrierInstance && $carrierInstance->isTrackingAvailable()) {
					$package->TrackingProviderId = $mcTrack->getCarrierCode();
					$package->TrackingProviderName = $carrierInstance->getConfigData('title');
				}
			}
			$package->Weight = $this->buildPackageWeight($mcShipment);
			$package->Warehouse = $this->buildWarehouseAddress($mcShipment->getStore());

			if (count($mcShipment->getAllTracks()) > 1) {
				$package->Items = null;
				$package->Weight = 0;
				$packageResult->Message = "There are no items shown in this package because the Magento shipment was split into more than one package and Magento does not record which items were in each package.";
			}

			$packageResult->Success = true;
		}
		catch (Exception $ex)
		{
			$packageResult->Success = false;
			$packageResult->Message = $ex->getMessage();
		}

		return $packageResult;
	}

	/**
	 * Add up the item weights;
	 *
	 * @param Mage_Sales_Model_Order_Shipment $mcShipment
	 * @return float
	 */
	private function buildPackageWeight(Mage_Sales_Model_Order_Shipment $mcShipment)
	{
		$value = (float) 0;
		foreach ($mcShipment->getAllItems() as $mcShipmentItem)
		{
			$mcOrderItem = $mcShipmentItem->getOrderItem();
			if (!$mcOrderItem->isDummy(true)) {
				$value += $mcShipmentItem->getQty() * $mcOrderItem->getWeight();
			}
		}
		return (float) $value;
	}

	/**
	 * Magento does not have a built-in payment status, so this calculates
	 * a reasonable value.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return string
	 */
	private function buildPaymentStatus(Mage_Sales_Model_Order $mcOrder)
	{
		if ($mcOrder->getTotalDue() == 0)
			return Mage::getStoreConfig(self::PAYMENT_STATUS_PAID, $mcOrder->getStore());
		if (abs($mcOrder->getGrandTotal() - $mcOrder->getTotalDue()) < 0.001)
			return Mage::getStoreConfig(self::PAYMENT_STATUS_UNPAID, $mcOrder->getStore());
		return Mage::getStoreConfig(self::PAYMENT_STATUS_PARTIALLY_PAID, $mcOrder->getStore());
	}

    /**
     * Extract the shipping method name from the shipment
     * @param Mage_Sales_Model_Order_Shipment $mcShipment
     * @return string
     */
    private function buildRateOptionName(Mage_Sales_Model_Order_Shipment $mcShipment)
    {
        $method = $mcShipment->getOrder()->getShippingMethod();
        $data = explode('_', $method);
        $carrierCode = $data[0];
        $className = Mage::getStoreConfig('carriers/'.$carrierCode.'/model');
        if ($className) {
            $carrierModel = Mage::getModel($className);
            foreach ($carrierModel->getAllowedMethods() as $key => $name)
            {
                if ($key == $data[1]) return $name;
            }
        }
    }

    /**
     * Extract the shipping provider name from the shipment
     * @param Mage_Sales_Model_Order_Shipment $mcShipment
     * @return string
     */
    private function buildRateProviderName(Mage_Sales_Model_Order_Shipment $mcShipment)
    {
        return $mcShipment->getOrder()->getShippingCarrier()->getConfigData('title');
    }

    /**
     * Create a shipment with the remaining items in the order.
     *
     * @param Mage_Sales_Model_Order $mcOrder
     * @return Mage_Sales_Model_Order_Shipment
     */
    private function buildShipment(Mage_Sales_Model_Order $mcOrder)
    {
    	if (!$mcOrder->canShip()) {
    		return null;
    	}

        $converter = Mage::getModel('sales/convert_order');
        $mcShipment = $converter->toShipment($mcOrder);
        $totalQty = 0;
        foreach ($mcOrder->getAllItems() as $mcOrderItem) {
        	if ($mcOrderItem->isDummy(true) || $mcOrderItem->getIsVirtual()) {
        		continue;
        	}
            
            $qty = $mcOrderItem->getQtyToShip();
            if ($qty > 0) {
                $mcOrderShipmentItem = $converter->itemToShipmentItem($mcOrderItem);
                $mcOrderShipmentItem->setQty($qty);
                $mcShipment->addItem($mcOrderShipmentItem);
                $totalQty += $qty;
            }
        }
        if ($totalQty > 0) {
            $mcShipment->setTotalQty($totalQty);
            return $mcShipment;
        }
        return null;
    }

	/**
	 * Build a shipping address from an order address array.
	 * Note that Shipper expects 2 letter country, state, and
	 * province codes.
	 * @param Mage_Sales_Model_Order_Address $mcAddress
	 * @return ShippingAddress
	 */
	private function buildShippingAddress(Mage_Sales_Model_Order_Address $mcAddress)
	{
		$address = new ShippingAddress();
		$address->AddressId = $mcAddress->getId();
		$address->City = $mcAddress->getCity();
		$address->Company = $mcAddress->getCompany();
		$address->Country = $mcAddress->getCountry();
		$address->Email = $mcAddress->getEmail();
		$address->Fax = $mcAddress->getFax();
		$address->FirstName = $mcAddress->getFirstname();
		$address->LastName = $mcAddress->getLastname();
		$address->Line1 = $mcAddress->getStreet1();
		$address->Line2 = $mcAddress->getStreet2();
		$address->Phone = $mcAddress->getTelephone();
		$address->PostalCode = $mcAddress->getPostcode();
		$address->Region = $mcAddress->getRegionCode();
        $address->Residential = Mage::getStoreConfig(self::RESIDENTIAL,
            $mcAddress->getOrder()->getStore());
		return $address;
	}

	/**
	 * Magento does not have a shipping status. This function calculates
	 * a reasonable value.
	 * @param Mage_Sales_Model_Order $mcOrder
	 * @return string
	 */
	private function buildShippingStatus(Mage_Sales_Model_Order $mcOrder)
	{
        if ($mcOrder->getIsVirtual())
        {
            return Mage::getStoreConfig(self::SHIPPING_STATUS_UNSHIPPABLE, $mcOrder->getStore());
        }

        $qtyOrdered = 0;
        $qtyToShip = 0;
        foreach ($mcOrder->getAllItems() as $mcItem)
        {
        	if ($mcItem->getParentItem())
        	{
        		continue;
        	}
        	if ($mcItem->getIsVirtual())
        	{
        		continue;
        	}
			$qtyOrdered += $mcItem->getQtyOrdered();
			$qtyToShip += $mcItem->getQtyToShip();
        }

        if ($qtyOrdered == 0)
        {
            return Mage::getStoreConfig(self::SHIPPING_STATUS_UNSHIPPABLE, $mcOrder->getStore());
        }
        if ($qtyToShip == 0)
        {
            return Mage::getStoreConfig(self::SHIPPING_STATUS_SHIPPED, $mcOrder->getStore());
        }
        if ($qtyOrdered == $qtyToShip)
        {
            return Mage::getStoreConfig(self::SHIPPING_STATUS_UNSHIPPED, $mcOrder->getStore());
        }
        return Mage::getStoreConfig(self::SHIPPING_STATUS_PARTIALLY_SHIPPED, $mcOrder->getStore());
    }

	/**
	 * Update the package with shipping information from Shipper.
	 * @param stdClass $update
	 * @return UpdateResult
	 */
	private function buildUpdateResult(stdClass $update)
	{
		$notes = array();

		$updateResult = new UpdateResult();
		try
		{
			$mcOrder = Mage::getModel('sales/order')->load($update->OrderId);
			if ($mcOrder->getId() != $update->OrderId)
				throw new Exception("Invalid Order ID '" . $update->OrderId . "'.");
                
            if (isset($update->PackageId) && strpos($update->PackageId, ",")) {
                $ids = explode(",", $update->PackageId);
                $mcShipmentId = trim($ids[0]);
                $mcTrackId = trim($ids[1]);
            }

            if (isset($mcShipmentId)) {
                $mcShipment = Mage::getModel('sales/order_shipment')->load($mcShipmentId);
                if ($mcShipment->getId() != $mcShipmentId) {
                    throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
            } else {
                $mcShipment = $this->buildShipment($mcOrder);
                if ($mcShipment == null) {
                    throw new Exception("Shipment not recorded because the order was already completely shipped.");
                }
            }

            if (isset($mcTrackId)) {
                $mcTrack = Mage::getModel("sales/order_shipment_track")->load($mcTrackId);
                if ($mcTrack->getId() != $mcTrackId || $mcTrack->getShipment()->getId() != $mcShipmentId) {
                    throw new Exception("Invalid Package ID '" . $parameters->packageId . "'.");
                }
            } else if (count($mcShipment->getAllTracks()) == 0) {
                $mcTrack = Mage::getModel("sales/order_shipment_track");
                $mcShipment->addTrack($mcTrack);
            } else {
                $mcTracks = $mcShipment->getAllTracks();
                $mcTrack = $mcTracks[0];
            }

			// Shipper can correct the shipping address (e.g. fix typos and find
			// the ZIP+4 code). If the destination address is included in the update
			// this code will update the order address. You may want to update the
			// customers address book as well.
            $mcShippingAddress = $mcOrder->getShippingAddress();
			if (isset($update->Destination) && !$this->addressesAreEqual($update->Destination, $mcShippingAddress))
			{
                if (isset($update->Destination->City)) {
                    $mcShippingAddress->setCity($update->Destination->City);
                } else {
                    $mcShippingAddress->setCity("");
                }
                if (isset($update->Destination->Company)) {
                    $mcShippingAddress->setCompany($update->Destination->Company);
                } else {
                    $mcShippingAddress->setCompany("");
                }
                if (isset($update->Destination->Country)) {
                    $mcShippingAddress->setCountry($update->Destination->Country);
                } else {
                    $mcShippingAddress->setCountry("");
                }
                if (isset($update->Destination->Email)) {
                    $mcShippingAddress->setEmail($update->Destination->Email);
                } else {
                    $mcShippingAddress->setEmail("");
                }
                if (isset($update->Destination->Fax)) {
                    $mcShippingAddress->setFax($update->Destination->Fax);
                } else {
                    $mcShippingAddress->setFax("");
                }
                if (isset($update->Destination->FirstName)) {
                    $mcShippingAddress->setFirstname($update->Destination->FirstName);
                } else {
                    $mcShippingAddress->setFirstname("");
                }
                if (isset($update->Destination->LastName)) {
                    $mcShippingAddress->setLastname($update->Destination->LastName);
                } else {
                    $mcShippingAddress->setLastname("");
                }
                if (isset($update->Destination->PostalCode)) {
                    $mcShippingAddress->setPostcode($update->Destination->PostalCode);
                } else {
                    $mcShippingAddress->setPostcode("");
                }
                if (isset($update->Destination->Region)) {
                    $mcShippingAddress->setRegionCode($update->Destination->Region);
                } else {
                    $mcShippingAddress->setRegionCode("");
                }
                $street = array();
                if (isset($update->Destination->Line1)) {
                    $street[] = $update->Destination->Line1;
                }
                if (isset($update->Destination->Line2)) {
                    $street[] = $update->Destination->Line2;
                }
                $mcShippingAddress->setStreet($street);
                if (isset($update->Destination->Phone)) {
                    $mcShippingAddress->setTelephone($update->Destination->Phone);
                } else {
                    $mcShippingAddress->setTelephone("");
                }
				$mcShippingAddress->save();
				$notes[] = "Updated order shipping address.";
			}

			// If Shipper sends a ship date, that means the package was shipped.
			if (isset($update->ShipDate))
			{
                $shipDate = date_create($update->ShipDate)->format("Y-m-d H:i:s");
                if (!$mcShipment->hasCreatedAt()) {
                    $mcShipment->setCreatedAt($shipDate);
                    $mcShipment->save();
                }
				$notes[] = "Package was shipped on " . $shipDate . ".";

                if (isset($update->TrackingNumber))
                {
                    if (isset($update->TrackingProviderId)) {
                        $mcTrack->setCarrierCode($update->TrackingProviderId);
                        $mcCarrier = Mage::getSingleton('shipping/config')->getCarrierInstance($mcTrack->getCarrierCode());
                        if ($mcCarrier) {
                            $mcTrack->setTitle($mcCarrier->getConfigData('title'));
                        }
                    } else {
                        $mcTrack->setCarrierCode(Mage_Sales_Model_Order_Shipment_Track::CUSTOM_CARRIER_CODE);
                    }
                    $mcTrack->setNumber($update->TrackingNumber);
                    $mcTrack->save();
                    $notes[] = "Tracking number set to " . $update->TrackingNumber . ".";
                }

                if (isset($update->ShippingCost))
                {
                    $notes[] = "Shipping cost was " . $mcOrder->formatPriceTxt($update->ShippingCost) . ".";
                }

                foreach ($mcShipment->getAllItems() as $mcOrderShipmentItem) {
                    $mcOrderItem = $mcOrderShipmentItem->getOrderItem();
                    $qtyShipped = $mcOrderItem->getQtyShipped() + $mcOrderShipmentItem->getQty();
                    $mcOrderItem->setQtyShipped($qtyShipped);
                    $mcOrderItem->save();
                }

                if (count($notes) > 0)
                {
                    foreach ($notes as $note) {
                        $mcShipment->addComment($note);
                    }
                }
                $this->saveShipment($mcShipment);
                
                if (Mage::getStoreConfig(self::SEND_SHIPMENT_NOTICE, $mcShipment->getStore()))
                {
                    $mcShipment->sendEmail(true)->setEmailSent(true)->save();
                }
            }

			// Return a fresh copy of the order, the package, and the notes.
			$mcOrder->load($update->OrderId);
			$updateResult->OrderResult = $this->buildOrderResult($mcOrder, false);
            $updateResult->PackageResult = $this->buildPackageResult($mcTrack);
			if (count($notes) > 0)
				$updateResult->Message = implode(" ", $notes);
			else
				$updateResult->Message = "No change.";
			$updateResult->Success = true;
		}
		catch (Exception $ex)
		{
			$updateResult->Message = $ex->getMessage();
			$updateResult->Success = false;
		}

		return $updateResult;
	}

	private function buildWarehouseAddress(Mage_Core_Model_Store $mcStore)
	{
		$warehouse = new Address();
		$warehouse->AddressId = $mcStore->getId();
		$warehouse->City = $mcStore->getConfig("shipping/origin/city");
		$warehouse->Country = $mcStore->getConfig("shipping/origin/country_id");
		$warehouse->PostalCode = $mcStore->getConfig("shipping/origin/postcode");

		$regions = Mage::getModel("directory/country")->setId($warehouse->Country)->getRegions();
		$regionId = $mcStore->getConfig("shipping/origin/region_id");
		foreach ($regions as $region) {
			if ($region->getId() ==  $regionId) {
				$warehouse->Region = $region->getName();
				break;
			}
		}
		return $warehouse;
	}

	private function getFloat($text)
	{
		if (!isset($text) || trim($text) == '') return (float) 0;
		$nocommas = str_replace(",", "", $text);
		$amount = str_replace("$", "", $nocommas);
		return (float) $amount;
	}

    public function getSoapServer()
    {
        $server = new SoapServer(dirname(__FILE__) . "/shippingagent.wsdl");
        $server->setClass("StructuredSolutions_ShippingAgent_Model_Server");
        return $server;
    }
 
    private function saveShipment($shipment)
    {
        $shipment->getOrder()->setIsInProcess(true);
        $transactionSave = Mage::getModel('core/resource_transaction')
            ->addObject($shipment)
            ->addObject($shipment->getOrder())
            ->save();

        return $this;
    }
}
?>